package com.example.thread;

import java.util.ArrayList;
import java.util.List;

// 本例演示的是消费者生产者模式如何手动实现。
// 消费者生产者模式常常应用于多线程场景中，在并发编程中是非常常见的一种模式。解耦了生产和消费的逻辑。
// 先复习下和线程状态相关的东西，这个对于理解多线程的相关代码，非常重要。
// 线程状态一头一尾有：被创建、被终止。这两个我们不管，我们关注中间大致来分其实有三种状态：
// 可运行：获得了锁的占有权。(可运行表示可以运行。但是是否正在运行，取决于cpu的调度，因为还有其他进程的线程要执行啊。)
// 锁阻塞：没有获得锁的占有权。(它和等待状态是两回事哦)
// 等待：调用了wait、sleep等方法。(1)等待状态，定时结束或者被别的线程唤醒之后，进入锁阻塞。(2)等待状态，定时结束或者被唤醒，同时还获取了锁的占有权，则进入可执行状态。
// 再回过头来看看
// 被创建：线程对象已经被创建出来，但是还没有执行start()方法之前。也就是还没有开始执行任务。
// 被终止：要么任务执行结束自动终止。要么手动调用 interrupt() 方法终止。要么就是线程执行任务的过程中有异常，但是没有捕获，也会终止线程，所以我们非常注意在线程任务中写一些try ... catch ... 之类的结构。在终止状态下，线程所占用的资源会被释放，线程对象也会被销毁。
// 测试区域
public class CustomerAndProducerTest {
    public static void main(String[] args) {

        // 新建商店
        Storage storage = new Storage();

        // 创建了四个生产者
        Producer p1 = new Producer(storage);
        Producer p2 = new Producer(storage);
        Producer p3 = new Producer(storage);
        Producer p4 = new Producer(storage);

        // 创建了3个消费者
        Customer c1 = new Customer(storage);
        Customer c2 = new Customer(storage);
        Customer c3 = new Customer(storage);

        //
        p1.setNum(10);
        p2.setNum(20);
        p3.setNum(80);

        c1.setNum(50);
        c2.setNum(20);
        c3.setNum(20);

        // 系统会对应地调用生产者和消费者线程里面的run方法。
        c1.start();
        c2.start();
        c3.start();
        p1.start();
        p2.start();
        p3.start();
    }
}


class Storage {

    // 静态变量区
    private static int MAX_VALUE = 100;

    // 成员变量区
    private List<Object> list = new ArrayList<>();

    // 成员方法。接收一个数字。不返回。
    public void produce(int num) {
        synchronized (list) { // 用list配合synchronized来用，list当做锁
            // list.size()是指list里面已经装了的东西哦。list.size() + num应该指代已经装的东西和接下来打算装的num个东西。不能超过MAX_VALUE。
            while (list.size() + num > MAX_VALUE) {
                System.out.println("暂时不能执行生产任务");
                try {
                    list.wait(); // 此操作会让当前线程进入等待状态。直到它被其他线程唤醒???它进入等待状态是因为产品库存快要满了。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 开始生产产品了。
            for (int i = 0; i < num; i++) { // 给list里面添加num个对象。
                list.add(new Object());
            }
            // 生产产品完毕，打印一下当前仓库状态。
            System.out.println("已生产产品数" + num + " 仓库容量" + list.size());
            // 唤醒其他处于等待中的线程。
            list.notifyAll();
        }
    }

    public void consume(int num) {
        synchronized (list) { // 消费方法，和produce方法用的同一把锁。
            while (list.size() < num) { // num是客人点的单的数量。list.size()是仓库库存。因为整个订单是一个整体一比的，库存不够那么就先不消费。
                System.out.println("暂时不能执行消费任务");
                try {
                    list.wait(); // 让线程进入等待状态。它进入等待状态是因为产品库存不够。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 开始消费num个产品
            for (int i = 0; i < num; i++) {
                list.remove(0);
            }
            // 消费完毕之后打印一下仓库状态。
            System.out.println("已消费产品数" + num + " 仓库容量" + list.size());
            // 唤醒其他处于等待状态的线程。
            list.notifyAll();
        }
    }
}


// 生产者
class Producer extends Thread { // 生产者单独用一个线程。

    // 成员变量区
    private int num;
    private Storage storage;

    public Producer(Storage storage) {
        this.storage = storage;
    }

    public void setNum(int num) {
        this.num = num;
    }

    // 成员方法
    public void run() {
        storage.produce(this.num); // 调用storage里面的produce方法。就是调用商店的生产方法吗
    }
}


// 消费者
class Customer extends Thread { // 消费者单独用另一个线程。

    // 成员变量区
    private int num;
    private Storage storage;

    // 消费者的构造方法。
    // 传storage进来，是因为，run方法要调用storage.consume(this.num);
    public Customer(Storage storage) {
        this.storage = storage;
    }

    public void setNum(int num) {
        this.num = num;
    }

    // 在Java中，@Override注解通常用于标记一个方法是覆盖了父类中的方法，以确保编译器能够检查到是否正确地覆盖了父类的方法。但是在上述代码中，Customer类是直接继承自Thread类，并没有覆盖Thread类的run()方法，而是新增了一个run()方法，因此不需要添加@Override注解。
    //如果你的意图是覆盖Thread类的run()方法，你才应该添加@Override注解，然后编译器会检查你的run()方法是否正确地覆盖了父类Thread的run()方法。
    // 成员方法。重新自己定义了一个run方法。
    // 当Customer的一个实例对象，调用start方法的时候，系统会自动调用它的run方法。
    public void run() {
        storage.consume(this.num);
    }
}






// 后记：java中的消费者和生产者模式有什么牛逼之处
// 消费者和生产者模式是一种经典的多线程设计模式，通常用于解决生产者和消费者之间的数据共享和同步问题。这种模式的牛逼之处在于它可以有效地解决多线程环境下的资源竞争和同步问题，确保线程安全和高效利用资源。
//下面是一些消费者和生产者模式的牛逼之处：
//1. **解耦生产者和消费者**：生产者和消费者之间通过一个共享的缓冲区进行通信，彼此之间无需直接交互，这种解耦使得系统更加灵活，易于维护和扩展。
//2. **避免资源竞争**：通过合理设计缓冲区，生产者和消费者可以独立地操作数据，避免了资源竞争问题，提高了系统的并发性能。
//3. **实现同步与互斥**：生产者和消费者模式通过同步机制确保线程安全，例如通过使用锁或信号量来控制对共享资源的访问，从而避免了数据的不一致性和错误。
//4. **提高系统吞吐量**：通过允许多个生产者和消费者并发执行，可以更有效地利用系统资源，提高系统的吞吐量和性能。
//5. **支持异步通信**：生产者和消费者模式可以支持异步通信，即生产者和消费者可以以不同的速度进行数据生产和消费，从而提高了系统的灵活性和响应能力。
//总的来说，消费者和生产者模式是一种非常强大的并发编程模式，可以帮助开发人员设计高效、稳定和可扩展的多线程应用程序。
